package gui.batches;

import gui.batches.command.AddItemsCommand;
import gui.batches.command.CommandHistory;
import gui.common.Controller;
import gui.common.IView;
import gui.inventory.ProductContainerData;
import gui.item.ItemData;
import gui.product.ProductData;
import hit.core.Barcode;
import hit.core.HomeInventoryTracker;
import hit.core.Item;
import hit.core.ItemFacade;
import hit.core.Product;
import hit.core.ProductFacade;
import hit.core.StorageUnit;
import hit.dao.DataAccessObject;
import hit.dao.FactoryReference;
import hit.util.BarcodePrinter;
import hit.util.DataConverter;

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.Action;
import javax.swing.Timer;

/**
 * Controller class for the add item batch view.
 */
public class AddItemBatchController extends Controller implements
		IAddItemBatchController, Action {
	
	private static final int BARCODE_TIMEOUT = 750;
	private static final String BARCODE_FILE = "itemcodes.pdf";

	ItemFacade itemFacade;
	ProductFacade productFacade;
	CommandHistory commandHistory;
	
	ProductContainerData target;
	int count;
	Barcode barcode;
	
	boolean useTimer;
	Timer timer;
	
	List<Item> newItems;
	Map<ProductData, List<ItemData>> dataMapping;
	List<ProductData> pdatasInOrder;
	
	/**
	 * Constructor.
	 * 
	 * @param view Reference to the add item batch view.
	 * @param target Reference to the storage unit to which items are being added.
	 */
	public AddItemBatchController(IView view, ProductContainerData target) {
		super(view);
		
		itemFacade = HomeInventoryTracker.Instance().GetItemFacade();
		productFacade = HomeInventoryTracker.Instance().GetProductFacade();
		commandHistory = new CommandHistory();
		
		count = 1;
		barcode = null;
		this.target =  target;
		
		useTimer = true;
		timer = null;
		
		newItems = new ArrayList<Item>();
		dataMapping = new HashMap<ProductData, List<ItemData>>();
		pdatasInOrder = new ArrayList<ProductData>();
		
		construct();
	}

	/**
	 * Returns a reference to the view for this controller.
	 */
	@Override
	protected IAddItemBatchView getView() {
		return (IAddItemBatchView) super.getView();
	}

	/**
	 * Loads data into the controller's view.
	 * 
	 *  {@pre None}
	 *  
	 *  {@post The controller has loaded data into its view}
	 */
	@Override
	protected void loadValues() {
		getView().setUseScanner(useTimer);
		getView().setCount(Integer.toString(count));
	}

	/**
	 * Sets the enable/disable state of all components in the controller's view.
	 * A component should be enabled only if the user is currently
	 * allowed to interact with that component.
	 * 
	 * {@pre None}
	 * 
	 * {@post The enable/disable state of all components in the controller's view
	 * have been set appropriately.}
	 */
	@Override
	protected void enableComponents() {
		boolean result = (!useTimer &&
							getView().getEntryDate() != null && 
							count >= 1 &&
							Barcode.IsValid(barcode));
		
		//if the entry date is in the future, don't enable ok
		if (getView().getEntryDate() != null &&
				getView().getEntryDate().after(new Date()))
			result = false;
		
		getView().enableItemAction(result);
		
		//check if undo should be enabled
		if (commandHistory.CanUndo())
			getView().enableUndo(true);
		else
			getView().enableUndo(false);
		
		//check if redo should be enabled
		if (commandHistory.CanRedo()) 
			getView().enableRedo(true);
		else
			getView().enableRedo(false);
	}

	/**
	 * This method is called when the "Entry Date" field in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void entryDateChanged() {
		enableComponents();
	}

	/**
	 * This method is called when the "Count" field in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void countChanged() {
		try {
			count = Integer.parseInt(getView().getCount());
		}
		catch (Exception e) {
			count = 0;
		}
		finally {
			enableComponents();
		}
	}

	/**
	 * This method is called when the "Product Barcode" field in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void barcodeChanged() {
		
		if (timer != null) {
			timer.stop();
			timer = null;
		}
		
		try {
			String newBarcodeValue = getView().getBarcode();  
			if (newBarcodeValue.equals(""))
			{
				barcode = null;
			}
			else
			{
				if (useTimer)
				{
					timer = new Timer(BARCODE_TIMEOUT, this);
					timer.start();
				}
				
				barcode = new Barcode(getView().getBarcode());
			}
		}
		catch(Exception e) {
			barcode = null;
		}
		finally {
			enableComponents();
		}
	}
	
	public void actionPerformed(ActionEvent e) {
		
		if (Barcode.IsValid(barcode) && count >= 1)
			addItem();
		else if (count == 0) {
			getView().setBarcode("");
			getView().setCount("1");
			getView().displayErrorMessage("Invalid Count");
		}
		else{
			getView().setBarcode("");
			getView().displayErrorMessage("Invalid Barcode.");
		}
		
		if (timer != null && timer.isRunning())
			timer.stop();
		
		timer = null;
	}

	/**
	 * This method is called when the "Use Barcode Scanner" setting in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void useScannerChanged() {
		
		useTimer = getView().getUseScanner();
		
		if (useTimer) {
			barcode = null;
			getView().setBarcode("");
		}
		else {
			if (timer != null)
			{
				timer.stop();
				timer = null;
			}
		}
		
		enableComponents();
	}

	/**
	 * This method is called when the selected product changes
	 * in the add item batch view.
	 */
	@Override
	public void selectedProductChanged() {
		// Change the list of items to draw.
		
		DrawItems();
	}

	/**
	 * This method is called when the user clicks the "Add Item" button
	 * in the add item batch view.
	 */
	@Override
	public void addItem() {
		try {
			Product product = productFacade.GetProduct(barcode.GetValue());
						
			if (product == null)
			{
				// Couldn't find. Open Add Product dialog and then check again.
				getView().displayAddProductView();
				
				product = productFacade.GetProduct(barcode.GetValue());
				
				//if they pushed cancel, undo all the barcode changes
				if (product == null) {
					getView().setBarcode("");
					barcode = null;
					getView().setCount("1");
					getView().setEntryDate(new Date());
					enableComponents();
					return;
				}
			}
			
			Date entryDate = getView().getEntryDate();
			
			//create all the items that need to be added to the HIT and then
			//have the CommandHistory execute the Command
			List<Item> items = new ArrayList<Item>();
			for (int i = 0; i < count; i++) {
				items.add(product.CreateItem((StorageUnit) target.getTag(), entryDate));
			}
			commandHistory.Execute(new AddItemsCommand(items));
			newItems.addAll(items);
			
			UpdateView(product, items, true);
			
			//reset the view
			barcode = new Barcode();
			getView().setBarcode("");
			count = 1;
			getView().setCount("1");
			getView().setEntryDate(new Date());
			
			getView().giveBarcodeFocus();
		}
		catch(Exception ex) {
			getView().displayErrorMessage("Failed to create items.");
		}
		
		enableComponents();
	}
	
	/**
	 * Updates the Product and Item tables
	 * @param product the Product that was updated by the operation
	 * @param items the List of Items updated by the operation
	 * @param added true if Items were added, false if they were removed
	 * */
	private void UpdateView(Product product, List<Item> items, boolean added) {
		List<ItemData> itemDatas = DataConverter.TranslateItems(items);
		
		// Find the existing ProductData for the product
		ProductData productData = null;
		Iterator<ProductData> iter = dataMapping.keySet().iterator();
		
		while (iter.hasNext())
		{
			ProductData temp = iter.next();
			if (temp.getTag() == product)
			{
				productData = temp;
				break;
			}
		}
		
		if (productData == null)
		{
			productData = DataConverter.TranslateProduct(product);
			dataMapping.put(productData, itemDatas);
			pdatasInOrder.add(productData);
		}
		else
		{
			if (added)
				dataMapping.get(productData).addAll(itemDatas);
			else 
				dataMapping.get(productData).removeAll(itemDatas);
		}
		
		String newCount = Integer.toString(dataMapping.get(productData).size());
		productData.setCount(newCount);
		
		//if the product is completely gone, remove it from the dataMapping
		//and clear the item list
		if (dataMapping.get(productData).size() == 0) {
			dataMapping.remove(productData);
			pdatasInOrder.remove(productData);
			getView().setItems(new ItemData[0]);
		}
		
		ProductData prevSelected = getView().getSelectedProduct();
		
		ProductData[] displayProducts = new ProductData[dataMapping.keySet().size()];
		displayProducts = pdatasInOrder.toArray(displayProducts);
		getView().setProducts(displayProducts);
		
		if (prevSelected != null) {
			getView().selectProduct(prevSelected);
		
			if (getView().getSelectedProduct() == productData)
				DrawItems();
		}
		else
		{
			getView().setItems(new ItemData[0]);
		}
	}
	
	private void DrawItems() {
		
		ProductData selectedProduct = getView().getSelectedProduct();
		
		List<ItemData> selectedItems = dataMapping.get(selectedProduct);
		if (selectedItems == null) {
			
			getView().displayErrorMessage("Error retrieving list of new items.");
			return;
		}
		
		ItemData[] selectedItemArray = new ItemData[selectedItems.size()];
		selectedItemArray = selectedItems.toArray(selectedItemArray);
		getView().setItems(selectedItemArray);
	}
	
	/**
	 * This method is called when the user clicks the "Redo" button
	 * in the add item batch view.
	 */
	@Override
	public void redo() {
		List<Item> items = commandHistory.Redo();
		UpdateView(items.get(0).GetProduct(), items, true);
		newItems.addAll(items);
		
		//now reset the next item index
		HomeInventoryTracker.Instance().SetNextItemID(
				HomeInventoryTracker.Instance().GetNextItemID() + items.size());
		
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Undo" button
	 * in the add item batch view.
	 */
	@Override
	public void undo() {
		List<Item> items = commandHistory.Undo();
		UpdateView(items.get(0).GetProduct(), items, false);
		newItems.removeAll(items);
		
		//now reset the next item index
		HomeInventoryTracker.Instance().SetNextItemID(
				HomeInventoryTracker.Instance().GetNextItemID() - items.size());
		
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Done" button
	 * in the add item batch view.
	 */
	@Override
	public void done() {
		
		if (newItems.size() > 0) {

			ArrayList<Product> productsUpdated = new ArrayList<Product>();
			
			try
			{
				DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
				dao.StartTransaction();
				
				for (Item i : newItems)
				{
					int id = dao.GetItemDAO().Create(i);
					i.setId(id);
					
					dao.EndTransaction(true);
					
					if (!productsUpdated.contains(i.GetProduct()))
					{
						productsUpdated.add(i.GetProduct());
					}
				}
				
				for (Product p : productsUpdated)
				{
					dao.GetProductDAO().Update(p);
					dao.EndTransaction(true);
				}
				
				dao.GetSettingsDAO().Update(HomeInventoryTracker.Instance());
				dao.EndTransaction(true);
				
				dao.CloseConnection();
			}
			catch (SQLException sqlEx)
			{
				getView().displayErrorMessage(sqlEx.getMessage());
			}
			
			try {
			
				File newFile = new File(AddItemBatchController.BARCODE_FILE);
				File output = BarcodePrinter.Print(newItems, newFile);
				
				java.awt.Desktop.getDesktop().open(output);
			}
			catch(Exception e){
				getView().displayErrorMessage("Failed while creating and opening barcodes.");
			}
		}
		
		getView().close();
	}

	@Override
	public void addPropertyChangeListener(PropertyChangeListener arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Object getValue(String arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isEnabled() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void putValue(String arg0, Object arg1) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void removePropertyChangeListener(PropertyChangeListener arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setEnabled(boolean arg0) {
		// TODO Auto-generated method stub
		
	}
	
}

